//  Raytracer Assignment week 4 
//	by Zse Sun and Gerard Knap 
//
//  Framework for a raytracer
//  File: raytracer.cpp
//
//  Created for the Computer Science course "Introduction Computer Graphics"
//  taught at the University of Groningen by Tobias Isenberg.
//
//  Author: Maarten Everts
//	  Zhe Sun
//
//  This framework is inspired by and uses code of the raytracer framework of 
//  Bert Freudenberg that can be found at
//  http://isgwww.cs.uni-magdeburg.de/graphik/lehre/cg2/projekt/rtprojekt.html 
//

#include "raytracer.h"
#include "object.h"
#include "geometric.h"
#include "material.h"
#include "light.h"
#include "image.h"
#include "yaml/yaml.h"
#include <ctype.h>
#include <fstream>
#include <assert.h>

// Functions to ease reading from YAML input
void operator >>(const YAML::Node& node, Triple& t);
Triple parseTriple(const YAML::Node& node);

void operator >>(const YAML::Node& node, Triple& t) {
    assert(node.size() == 3);
    node[0] >> t.x;
    node[1] >> t.y;
    node[2] >> t.z;
}

Triple parseTriple(const YAML::Node& node) {
    Triple t;
    node[0] >> t.x;
    node[1] >> t.y;
    node[2] >> t.z;
    return t;
}

Vector parseVector(const YAML::Node& node) {
    Vector t;
    node[0] >> t.x;
    node[1] >> t.y;
    node[2] >> t.z;
    return t;
}

Material* Raytracer::parseMaterial(const YAML::Node& node) {
    Material *m = new Material();
	if (node.FindValue("color")) {
		node["color"] >> m->color;
	} else {
		node["texture"] >> m->texture;
		m->color.set(1.0,0.8,0.0);
	}
    node["ka"] >> m->ka;
    node["kd"] >> m->kd;
    node["ks"] >> m->ks;
    node["n"] >> m->n;
	
    return m;
}

Object* Raytracer::parseObject(const YAML::Node& node) {
    Object *returnObject = NULL;
    std::string objectType;
    node["type"] >> objectType;

    if (objectType == "sphere") {
        Point pos;
		double angle = 0.0;
        node["position"] >> pos;
        double r;
        node["radius"] >> r;
		if(node.FindValue("angle")){
			node["angle"] >> angle;
		}
        Sphere *sphere = new Sphere(pos, r, angle);
        returnObject = sphere;
    }

    if (objectType == "plan") {
        Point pos;
        node["position"] >> pos;
        Point normalPos;
        node["normal"] >> normalPos;
        Plan *plan = new Plan(pos, normalPos);
        returnObject = plan;
    }

    if (objectType == "triangle") {
        Point v1, v2, v3;
        node["v1"] >> v1;
        node["v2"] >> v2;
        node["v3"] >> v3;
        Triangle *triangle = new Triangle(v1, v2, v3);
        returnObject = triangle;
    }

    if (objectType == "cylinder") {
        Point pos;
        node["position"] >> pos;
        double r;
        node["radius"] >> r;
        unsigned int axis;
        node["axis"] >> axis;
        Cylinder *cylinder = new Cylinder(pos, r, (Axis) axis);
        returnObject = cylinder;
    }

    if (returnObject) {
        // read the material and attach to object
        returnObject->material = parseMaterial(node["material"]);
    }

    return returnObject;
}

Light* Raytracer::parseLight(const YAML::Node& node) {
    Point position;
    node["position"] >> position;
    Color color;
    node["color"] >> color;
    return new Light(position, color);
}

/*
 * Read a scene from file
 */

bool shadowMode = false;
int maxRecursionDepth;
int superSampleFactor;

bool Raytracer::readScene(const std::string& inputFilename) {
    // Open file stream for reading and have the YAML module parse it
    std::ifstream fin(inputFilename.c_str());
    if (!fin) {
        cerr << "Error: unable to open " << inputFilename << " for reading." << endl;
        ;
        return false;
    }
    try {
        YAML::Parser parser(fin);
        if (parser) {
            YAML::Node doc;
            parser.GetNextDocument(doc);
            // Read scene configuration options
            // Here, we need judge if "RenderMode" exist, otherwise scene01.ymal will fail to interpret
            if (const YAML::Node * pMode = doc.FindValue("RenderMode")) {
                std::string configType;
                *pMode >> configType;
                std::cout << "Key 'RenderMode' exists, with value '" << configType << "'\n";
                if (configType == "zbuffer") {
                    scene = new ZBufferScene();
                } else if (configType == "normal") {
                    scene = new NormalBufferScene();
                } else if (configType == "gooch"){
                    scene = new GoochScene();
                } else {
                    scene = new PhongScene();
                }
            } else {
                std::cout << "Key 'RenderMode' doesn't exist, use Phong renderMode default\n";
                // Initialize a new scene anyway
                scene = new PhongScene();
            }

            if (const YAML::Node * pMode = doc.FindValue("GoochParameters")) {
              float b, y;
              (*pMode)["b"] >> b;
              scene->kBlue = Color(0.0, 0.0, b);
              (*pMode)["y"] >> y;
              scene->kYellow = Color(y, y, 0.0);
              (*pMode)["alpha"] >> scene->alpha;
              (*pMode)["beta"] >> scene->beta;
	          } 


            // Judge if in shadow mode  Shadows: true
            if (const YAML::Node * pMode = doc.FindValue("Shadows")) {
                std::string configType;
                *pMode >> configType;
                std::cout << "Key 'Shadows' exists, with value '" << configType << "'\n";
                shadowMode = configType == "true";
            } else {
                std::cout << "Key 'Shadows' doesn't exist, use shadow default\n";
                // Initialize a new scene anyway
                shadowMode = false;
            }

            // Determine maxRecursionDepth, default: 2
            if (const YAML::Node * pMode = doc.FindValue("MaxRecursionDepth")) {
                std::string configType;
                *pMode >> maxRecursionDepth;
                std::cout << "Key 'MaxRecursionDepth' exists, with value '" << maxRecursionDepth << "'\n";
            } else {
                std::cout << "Key 'MaxRecursionDepth' doesn't exist, use default value of 0(no reflection).\n";
                // no reflection
                maxRecursionDepth = 0;
            }

	    // Determine the super sample factor to implement anti-aliasing
            if (const YAML::Node * pMode = doc.FindValue("SuperSampling")) {
	      
                (*pMode)["factor"] >> superSampleFactor;
                std::cout << "Key 'SuperSampling' exists, with value '" << superSampleFactor << "'\n";
            } else {
                std::cout << "Key 'SuperSampling' doesn't exist, use default value of 1.\n";
                superSampleFactor = 1;
            }	    

	    // use eye
	    if (doc.FindValue("Eye")) {
	      scene->setEye(parseTriple(doc["Eye"]));
        // set default view size, up vecter and center point  
        scene->setViewArea(400,400);
	      scene->setViewCenterPoint(Vector(200,200,0));
        scene->setViewUpVector(Vector(0,1,0));
	    } else if (const YAML::Node * pMode = doc.FindValue("Camera")) {
	      // parse camera
	      
	      // parse eye
	      scene->setEye(parseTriple((*pMode)["eye"]));
	      
	      // parse image size
	      int w, h;
	      ((*pMode)["viewSize"])[0] >> w;
	      ((*pMode)["viewSize"])[1] >> h;
	      scene->setViewArea(w,h);
	      printf("Img size: w(%d)\nh(%d)\n", w, h);
	      
	      // parse view center
	      scene->setViewCenterPoint(parseTriple((*pMode)["center"]));
	      
	      // parse up vector
	      scene->setViewUpVector(parseVector((*pMode)["up"]));
	    } else {
	      // no eye or camera in yaml file, error
 	      return false;
	    }

            // Read and parse the scene objects
            const YAML::Node& sceneObjects = doc["Objects"];
            if (sceneObjects.GetType() != YAML::CT_SEQUENCE) {
                cerr << "Error: expected a sequence of objects." << endl;
                return false;
            }
            for (YAML::Iterator it = sceneObjects.begin(); it != sceneObjects.end(); ++it) {
                Object *obj = parseObject(*it);
                // Only add object if it is recognized
                if (obj) {
                    scene->addObject(obj);
                } else {
                    cerr << "Warning: found object of unknown type, ignored." << endl;
                }
            }

            // Read and parse light definitions
            const YAML::Node& sceneLights = doc["Lights"];
            if (sceneObjects.GetType() != YAML::CT_SEQUENCE) {
                cerr << "Error: expected a sequence of lights." << endl;
                return false;
            }
            for (YAML::Iterator it = sceneLights.begin(); it != sceneLights.end(); ++it) {
                scene->addLight(parseLight(*it));
            }
        }
        if (parser) {
            cerr << "Warning: unexpected YAML document, ignored." << endl;
        }
    } catch (YAML::ParserException& e) {
        std::cerr << "Error at line " << e.mark.line + 1 << ", col " << e.mark.column + 1 << ": " << e.msg << std::endl;
        return false;
    }

    cout << "YAML parsing results: " << scene->getNumObjects() << " objects read." << endl;
    return true;
}

void Raytracer::renderToFile(const std::string& outputFilename) {
    Image img(scene->getViewW(), scene->getViewH());
    cout << "Tracing..." << endl;
    scene->render(img);
    cout << "Writing image to " << outputFilename << "..." << endl;
    img.write_png(outputFilename.c_str());
    cout << "Done." << endl;
}
